<?php

App::uses('AppController', 'Controller');

class PrediccionesController extends AppController {
	public $components = array('Session', 'RequestHandler');
	public $models = array();
	public $uses = array('Ruta', 'Parada', 'RutaParada', 'WaypointAyuda', 'RutaCoordenada', 
						'Recorrido', 'RecorridoRealizado', 'RecorridoRealizadoHistorico');
	public $helpers = array('Html','Form');
	
	//Limites de velocidad
	private $PARAMETRO_velocidad_max = 120.0;   //Maxima velocidad local
	private $PARAMETRO_promedio_vel_max = 90.0; //Maxima velocidad promedio para un segmento
	
	//Zona donde se rechazan coordenadas lejanas a la ruta
	private $PARAMETRO_rechazo_min = 0.05;
	private $PARAMETRO_rechazo_max = 0.95;
	private $PARAMETRO_rechazo_dist = 0.100;
	
	public function index() {
	
    }
	
	/*
		Extraccion de las coordenadas de la ruta, guardarlas en DB
	*/
	public function extraer_coordenadas(){
		
		//Query de coordenadas
		$rutas = $this->Ruta->find('all', array('recursive' => 0));
		$id_ruta = 0;
		
		//Valores de retorno en la plantilla...
		$this->set('todas_rutas', $rutas);
		$this->set('id_ruta', $id_ruta);		
	}
	
	/*
		Guarda las coordenadas de una ruta...
	*/
	public function guardar_coordenadas(){
		if (array_key_exists('ruta_id', $this->request->data)){
			$ruta_id = intval($this->request->data['ruta_id']);
		} else {
			$ruta_id = 0;
		}
		if (array_key_exists('coord_str', $this->request->data)){
			$coord_str = $this->request->data['coord_str'];
		} else {
			$coord_str = '';
		}
		
		if ($coord_str == '' || $ruta_id == 0){
			//Falla...
			$resultado = 0;
		} else {
			//Intentar guardar....
			$coord_parts = explode(",", $coord_str);		
			$num_coords = count($coord_parts) / 2;
			
			//Borrar los registros previos...
			$this->RutaCoordenada->deleteAll(Array('RutaCoordenada.id_ruta' => $ruta_id));
			
			//Crear los registros nuevos...						
			$data_salvar = Array();
			for ($i = 0; $i < $num_coords; $i++){
				$latitud = floatval($coord_parts[$i * 2]);
				$longitud = floatval($coord_parts[$i * 2 + 1]);
				
				$nuevo_registro = Array();
				$nuevo_registro['id_ruta'] = $ruta_id;
				$nuevo_registro['orden'] = $i;
				$nuevo_registro['latitud'] = $latitud;
				$nuevo_registro['longitud'] = $longitud;
				
				$data_salvar[] = $nuevo_registro;
			}
			//...guardar.....
			$this->RutaCoordenada->saveMany($data_salvar, Array('atomic' => true));
				
			//Finalizado...
			$resultado = 1;
		}
		
		$this->set('resultado', $resultado);
	}
	
	/*
		Hace que cada registro obtenido por Cake se represente en un solo arreglo
	*/
	private function _simplificar_resultados($resultados_consulta){
		$nuevos_resultados = Array();
				
		foreach($resultados_consulta as $resultado_actual){
			//Se asume que cada registro contiene info de uno o mas Modelos
			$registro = Array();
			foreach($resultado_actual as $modelo => $valores_modelo){
				foreach ($valores_modelo as $nombre_campo => $valor){
					$registro[$nombre_campo] = $valor;
				}
			}
			
			$nuevos_resultados[] = $registro;
		}
		
		return $nuevos_resultados;
	}	
	
	/*
		Recupera informacion sobre la ruta ....
	*/
	public function obtener_paradas_ruta(){
		if (array_key_exists('id_ruta', $this->request->query)){
			$id_ruta = intval($this->request->query['id_ruta']);
		} else {
			$id_ruta = 0;			
		}
		
		//Obtener paradas...
		$paradas = $this->RutaParada->find('all', 
			array(
				'recursive' => 0, 
				'conditions' => array(
					'Parada.estado_parada' => 'activa',
					'RutaParada.id_ruta' => $id_ruta,
				),
				'fields' => array('Parada.id_parada', 'Parada.latitud', 'Parada.longitud', 'Parada.nombre_parada',
								  'RutaParada.orden_ruta', 'RutaParada.tipo_parada'),
				'order' => array('RutaParada.orden_ruta'),
			)
		);
		$paradas = $this->_simplificar_resultados($paradas);
		
		//Solicitar Waypoints de ayuda usando CakePHP pipeline
		$waypoints = $this->WaypointAyuda->find('all', 
			array('recursive' => -1, 'conditions'=> array('WaypointAyuda.id_ruta' => $id_ruta))
		);			
		$waypoints = $this->_simplificar_resultados($waypoints);
		
		//Obtener waypoints
		 $this->set('paradas', $paradas);
		 $this->set('waypoints', $waypoints);
	}
	
	/*
		Script para mostrar las coordenadas de un recorrido....
	*/
	public function mostrar_coordenadas(){
		//Query de coordenadas
		$rutas = $this->Ruta->find('all', array('recursive' => 0));
		$id_ruta = 0;
		
		//Valores de retorno en la plantilla...
		$this->set('todas_rutas', $rutas);
		$this->set('id_ruta', $id_ruta);
	}
	
	/*
		Simulacion, reproduce recorridos de la base de datos
	*/
	public function simulacion(){
		//Extraer rutas...
		$rutas = $this->Ruta->find('all', array('recursive' => 0));
		$rutas = $this->_simplificar_resultados($rutas);
		
		$this->set('todas_rutas', $rutas);
	}
	
	/*
		Obtiene ultima coordenada disponible a un determinado delta de tiempo
		durante las simulaciones de recorridos
	*/
	public function obtener_ultima_coordenada(){
		//Parametros generales....
		if (array_key_exists('id_recorrido_realizado', $this->request->query)){
			$id_recorrido_realizado = intval($this->request->query['id_recorrido_realizado']);
		} else {
			$id_recorrido_realizado = 0;			
		}
		if (array_key_exists('delta', $this->request->query)){
			$delta = intval($this->request->query['delta']);
		} else {
			$delta = 0;			
		}
	
		//Ver cuantas coordenadas quedan en la simulacion...
		$count_coords = $this->RecorridoRealizadoHistorico->query("SELECT count(*) as cuenta_siguientes
																		FROM recorrido_realizado_historico rrh
																			INNER JOIN (
																				SELECT rrh.id_recorrido_realizado, min(rrh.fecha_hora_guardado_servidor) as start_time
																				FROM recorrido_realizado_historico rrh
																				GROUP BY rrh.id_recorrido_realizado
																			) rst ON rst.id_recorrido_realizado = rrh.id_recorrido_realizado
																		WHERE rrh.id_recorrido_realizado = ".$id_recorrido_realizado."
																			AND rrh.fecha_hora_guardado_servidor > rst.start_time + (".$delta." / 86400.0)");
	
		
		$count_coords = $count_coords[0][0]['cuenta_siguientes'];
		
		$coords = $this->RecorridoRealizadoHistorico->query("SELECT TOP 1 rrh.id_recorrido_realizado_historico, rrh.fecha_hora_guardado_servidor, 
																rrh.fecha_hora_envio_servidor, rrh.latitud, rrh.longitud, rrh.velocidad
															FROM recorrido_realizado_historico rrh
																INNER JOIN (
																	SELECT rrh.id_recorrido_realizado, min(rrh.fecha_hora_guardado_servidor) as start_time
																	FROM recorrido_realizado_historico rrh
																	GROUP BY rrh.id_recorrido_realizado
																) rst ON rst.id_recorrido_realizado = rrh.id_recorrido_realizado
															WHERE rrh.id_recorrido_realizado = ".$id_recorrido_realizado."
																AND rrh.fecha_hora_guardado_servidor <= rst.start_time + (".$delta." / 86400.0)
															ORDER BY rrh.id_recorrido_realizado_historico DESC");		
		
		if (count($coords) > 0){
			$ultima_coordenada = $coords[0][0];
			$ultima_coordenada['valida'] = 1;
		} else {
			$ultima_coordenada['valida'] = 0;
		}
		
		//Esto solo se hace para detener simulacion....
		$ultima_coordenada['cuenta_coords'] = $count_coords;			
		
		$this->set('ultima_coordenada', $ultima_coordenada);
	}
	
	/*
		Obtener las coordenadas de ruta guardadas en la DB
	*/
	public function obtener_coordenadas_ruta(){
		if (array_key_exists('id_ruta', $this->request->query)){
			$id_ruta = intval($this->request->query['id_ruta']);
		} else {
			$id_ruta = 0;			
		}
		
		$coordenadas_ruta = $this->RutaCoordenada->find('all', 
			array(
				'recursive' => -1, 
				'conditions' => array(
					'RutaCoordenada.id_ruta' => $id_ruta,
				),
				'fields' => array('RutaCoordenada.orden', 'RutaCoordenada.latitud', 'RutaCoordenada.longitud', 
								  'RutaCoordenada.velocidad_promedio', 'RutaCoordenada.velocidad_desviacion'),
				'order' => array('RutaCoordenada.orden'),
			)
		);
		
		$coordenadas_ruta = $this->_simplificar_resultados($coordenadas_ruta);
		
		$this->set('coordenadas_ruta', $coordenadas_ruta);
	}
	
	/*
		Obtener los recorridos realizados para una determinada ruta
	*/
	public function obtener_recorridos_realizados_ruta(){
		if (array_key_exists('id_ruta', $this->request->query)){
			$id_ruta = intval($this->request->query['id_ruta']);
		} else {
			$id_ruta = 0;			
		}
		
		$recorridos_realizados = $this->RecorridoRealizado->find('all', 
			array(
				"recursive" => 0, 
				'conditions' => array(
					'Recorrido.id_ruta' => $id_ruta,
				),
				'fields' => array(
					'RecorridoRealizado.id_recorrido_realizado', 
					'RecorridoRealizado.fecha_salida', 
					'RecorridoRealizado.fecha_llegada'
				),
				'order' => array('RecorridoRealizado.id_recorrido_realizado'),
		));
		
		$recorridos_realizados = $this->_simplificar_resultados($recorridos_realizados);								 
		
		$this->set('recorridos_realizados', $recorridos_realizados);
	}	
	
	/*
		Recupera todas las coordenadas del GPS a partir de un recorrido realizado (multiples)		
	*/
	public function obtener_coordenadas_recorrido_realizado(){
		
		//Parametros ruta y minimo recorrido realizado....
		if (array_key_exists('id_ruta', $this->request->query)){
			$id_ruta = intval($this->request->query['id_ruta']);
		} else {
			$id_ruta = 0;			
		}
		if (array_key_exists('min_id_recorrido_realizado', $this->request->query)){
			$id_recorrido_realizado = intval($this->request->query['min_id_recorrido_realizado']);
		} else {
			$id_recorrido_realizado = 0;			
		}
		
		//Recuperar coordenadas del GPS para la ruta y recorridos seleccionados...
		/*
			SELECT rrh.id_recorrido_realizado_historico, rrh.latitud, rrh.longitud
			FROM recorrido_realizado_historico rrh
				INNER JOIN recorridos_realizados rr on rrh.id_recorrido_realizado = rr.id_recorrido_realizado
				INNER JOIN recorridos r ON r.id_recorrido = rr.id_recorrido
			WHERE rrh.id_recorrido_realizado > :id_recorrido_realizado
				AND r.id_ruta = :id_ruta
		*/
		$coordenadas_gps = $this->RecorridoRealizadoHistorico->query("SELECT rrh.id_recorrido_realizado_historico, rrh.latitud, rrh.longitud
																	FROM recorrido_realizado_historico rrh
																		INNER JOIN recorridos_realizados rr on rrh.id_recorrido_realizado = rr.id_recorrido_realizado
																		INNER JOIN recorridos r ON r.id_recorrido = rr.id_recorrido
																	WHERE rrh.id_recorrido_realizado >= ".$id_recorrido_realizado."
																		AND r.id_ruta = ".$id_ruta);
		/*
		$coordenadas_gps = $this->RecorridoRealizadoHistorico->find('all', 
			array(
				"recursive" => 0, 
				'conditions' => array(
					'RecorridoRealizadoHistorico.id_recorrido_realizado >=' => $id_recorrido_realizado,
					'Recorrido.id_ruta' => $id_ruta,
				),
				'fields' => array(
					'RecorridoRealizadoHistorico.id_recorrido_realizado_historico', 
					'RecorridoRealizadoHistorico.latitud', 
					'RecorridoRealizadoHistorico.longitud'
				),				
				'order' => array('RecorridoRealizado.id_recorrido_realizado'),
				'joins' => array(
					array(
						'table' => 'recorridos',
						'alias' => 'Recorrido',
						'type' => 'INNER',
						'conditions' => array(
							'Recorrido.id_recorrido = RecorridoRealizado.id_recorrido'
						)
					),
				),
		));		
		*/
		$coordenadas_gps = $this->_simplificar_resultados($coordenadas_gps);
		
		$this->set('coordenadas_gps', $coordenadas_gps);
	}		
		
	/*
		Recupera todas las coordenadas del GPS para un solo recorrido realizado...		
	*/
	public function obtener_coordenadas_simple_recorrido(){
		//Parametros: recorrido realizado....
		if (array_key_exists('id_recorrido_realizado', $this->request->query)){
			$id_recorrido_realizado = intval($this->request->query['id_recorrido_realizado']);
		} else {
			$id_recorrido_realizado = 0;			
		}
		
		//Recuperar coordenadas del GPS para el recorrido seleccionado...
		/*
			SELECT id_recorrido_realizado_historico, fecha_hora_guardado_servidor, 
				fecha_hora_envio_servidor, latitud, longitud, velocidad
			FROM recorrido_realizado_historico 
			WHERE id_recorrido_realizado = :id_recorrido_realizado
			ORDER BY id_recorrido_realizado_historico
		*/
		$coordenadas_gps = $this->RecorridoRealizadoHistorico->find('all', 
			array(
				"recursive" => -1, 
				'fields' => array(
					'RecorridoRealizadoHistorico.id_recorrido_realizado_historico', 
					'RecorridoRealizadoHistorico.fecha_hora_guardado_servidor', 
					'RecorridoRealizadoHistorico.fecha_hora_envio_servidor', 
					'RecorridoRealizadoHistorico.latitud', 
					'RecorridoRealizadoHistorico.longitud',
					'RecorridoRealizadoHistorico.velocidad',
				),
				'conditions' => array(
					'RecorridoRealizadoHistorico.id_recorrido_realizado' => $id_recorrido_realizado,					
				),				
				'order' => array('RecorridoRealizadoHistorico.id_recorrido_realizado_historico'),				
		));				
		$coordenadas_gps = $this->_simplificar_resultados($coordenadas_gps);
		
		$this->set('coordenadas_gps', $coordenadas_gps);
	}
	
	/*
		Script para entrenar prediccion recorrido....
	*/
	public function entrenar_ruta(){
		$rutas = $this->Ruta->find('all', array('recursive' => 0));
		$rutas = $this->_simplificar_resultados($rutas);
				
		if ($this->request->is('post')){
			$id_ruta = intval($this->request->data['ruta_id']);
			$id_recorrido_realizado = intval($this->request->data['id_recorrido_realizado']);
		
			//Recuperar los puntos de la ruta...
			$puntos_ruta = $this->RutaCoordenada->find('all', 
				array(
					'recursive' => -1, 
					'conditions' => array(
						'RutaCoordenada.id_ruta' => $id_ruta,
					),
					'fields' => array('RutaCoordenada.id_ruta_coordenadas', 'RutaCoordenada.orden', 
									'RutaCoordenada.latitud', 'RutaCoordenada.longitud'),
					'order' => array('RutaCoordenada.orden'),
				)
			);
			$puntos_ruta = $this->_simplificar_resultados($puntos_ruta);
			
			//...Recuperando los recorridos ...
			$recorridos = $this->RecorridoRealizado->find('all', 
			array(
				"recursive" => 0, 
				'conditions' => array(
					'Recorrido.id_ruta' => $id_ruta,
					'RecorridoRealizado.id_recorrido_realizado >=' => $id_recorrido_realizado,
				),
				'fields' => array(
					'RecorridoRealizado.id_recorrido_realizado', 
					'RecorridoRealizado.fecha_salida', 
					'RecorridoRealizado.fecha_llegada'
				),
				'order' => array('RecorridoRealizado.id_recorrido_realizado'),
			));		
			$recorridos = $this->_simplificar_resultados($recorridos);
		
			$entrenar = true;					
			
			if (count($puntos_ruta) == 0){
				$mensaje = "No se han guardado las coordenadas de la ruta en la base de datos. \n".
							" Deben guardarse las coordenadas antes de ejecutar entrenamiento";
				$entrenar = false;
			}
			if (count($recorridos) == 0){
				$mensaje = "No hay recorridos realizados para la ruta y recorrido seleccionados";
				$entrenar = false;
			}
		
			if ($entrenar){
				//Pre-procesamiento....
				//...Calcular la distancia entre cada par de puntos de la ruta...
				$ruta_distancias = Array();
				$n_coords_ruta = count($puntos_ruta);
				
				$total_distance = 0.0;
				for ($i = 0; $i < $n_coords_ruta - 1; $i++){
					$lat1 = $puntos_ruta[$i]['latitud'];
					$lon1 = $puntos_ruta[$i]['longitud'];
					$lat2 = $puntos_ruta[$i + 1]['latitud'];
					$lon2 = $puntos_ruta[$i + 1]['longitud'];
					
					$dist = $this->latlong_distance_km($lat1, $lon1, $lat2, $lon2);
					
					$ruta_distancias[$i] = $dist;
					
					$total_distance += $dist;
				}			
				
				//..Calcular el porcentage de ruta en cada punto
				$porcentages_ruta = Array();
				$porcentages_ruta[0] = 0.0;
				$suma_actual = 0.0;
				for ($i = 0; $i < $n_coords_ruta - 1; $i++){
					$suma_actual += $ruta_distancias[$i];
					$porcentages_ruta[$i + 1] = $suma_actual / $total_distance;
				}
				
				//PRIMERO, TOMAR CADA RECORRIDO		
				//	- APROXIMAR Y FILTRAR CAPTURAS GPS A PUNTOS A RUTA		
				//	- CALCULAR VELOCIDAD PROMEDIO ENTRE CAPTURAS GPS
				//  - CONTABILIDAR ESA VELOCIDAD PROMEDIO EN CADA PUNTO DE LA RUTA
				
				//para cada recorrido...		
				//...preparar....
				$velocidades_coord_ruta = Array();
				for ($i = 0; $i < $n_coords_ruta - 1; $i++){
					$velocidades_coord_ruta[$i] = Array();
				}
				
				foreach ($recorridos as $rr_idx => $recorrido){
					$id_recorrido = $recorrido['id_recorrido_realizado'];			
					
					//....Recuperar coordenadas del GPS del recorrido...								
					$coords = $this->RecorridoRealizadoHistorico->find('all', array(
							"recursive" => 0, 
							'conditions' => array(
								'RecorridoRealizadoHistorico.id_recorrido_realizado' => $id_recorrido,
							),
							'fields' => array(
								'RecorridoRealizadoHistorico.id_recorrido_realizado_historico as coord_id', 
								'RecorridoRealizadoHistorico.fecha_hora_envio_servidor as hora_enviado', 
								'RecorridoRealizadoHistorico.fecha_hora_solicitud_gps as hora_solicitado',
								'RecorridoRealizadoHistorico.fecha_hora_guardado_servidor as hora_guardado',
								'RecorridoRealizadoHistorico.latitud',
								'RecorridoRealizadoHistorico.longitud',
								'RecorridoRealizadoHistorico.velocidad',
							),
							'order' => array(
								'RecorridoRealizadoHistorico.fecha_hora_envio_servidor', 
								'RecorridoRealizadoHistorico.id_recorrido_realizado_historico',
							),
					));		
					$coords = $this->_simplificar_resultados($coords);
					$n_coords_rec = count($coords);
					
					if ($n_coords_rec == 0){
						continue;
					}
					
					//...Filtrar coordenadas....
					//...1) Coordenadas fuera de tiempo....
					//Detection de coordenadas fuera de posicion...			
					$filtradas = Array();
					$filtradas[] = $coords[0];
					$last_date = $coords[0]['hora_enviado'];
					for ($i = 1; $i < $n_coords_rec; $i++){
						if ($last_date < $coords[$i]['hora_enviado']){
							$filtradas[] = $coords[$i];
							$last_date = $coords[$i]['hora_enviado'];					
						} 
					}
					//echo "Coords: ".count($coords)." , new = ".count($filtradas)."<br />";			
					$coords = $filtradas;			
					$n_coords_rec = count($filtradas);			
					
					//...2) Coordenadas muy lejanas de la ruta...
					//... primero aproximar coordenadas a RUTA
					$puntos_cercanos_idx = Array();
					$puntos_cercanos_fac = Array();
					$filtradas = Array();
					$cuenta_descartados = 0;
					
					//... el primer punto de la ruta....
					$last_closest = $this->punto_cercano_ruta_abs($coords[0]['latitud'], $coords[0]['longitud'], $puntos_ruta);					
					$puntos_cercanos_idx[0] = $last_closest['index'];
					$puntos_cercanos_fac[0] = $last_closest['tfact'];			
					$tiempo1 = strtotime($coords[0]['hora_enviado']);
					$filtradas[] = $coords[0];					
					
					for ($i = 1; $i < $n_coords_rec; $i++){
						//Coordenada del recorrido...
						$lat_punto = $coords[$i]['latitud'];
						$lon_punto = $coords[$i]['longitud'];
					
						//Diferencia de tiempo
						$tiempo2 = strtotime($coords[$i]['hora_enviado']);			
						$delta_tiempo = $tiempo2 - $tiempo1;
						
						//Punto mas cercano...
						$curr_closest = $this->punto_cercano_ruta_rel($lat_punto, $lon_punto, $puntos_ruta, $last_closest['index'], 
																	  $last_closest['tfact'], $delta_tiempo);
						//Checar si filtrar por distancia...
						if ($curr_closest['distancia'] > $this->PARAMETRO_rechazo_dist &&
							$porcentages_ruta[$curr_closest['index']] >= $this->PARAMETRO_rechazo_min &&
							$porcentages_ruta[$curr_closest['index']] <= $this->PARAMETRO_rechazo_max){
							//Invalida, saltar coordenada....
							$cuenta_descartados++;
							
						} else {			
							//Coordenada valida....
							$filtradas[] = $coords[$i];
							$puntos_cercanos_idx[] = $curr_closest['index'];
							$puntos_cercanos_fac[] = $curr_closest['tfact'];
															   
							$last_closest = $curr_closest;													   
							$tiempo1 = $tiempo2;
						}						
					}
					
					$coords = $filtradas;
					$n_coords_rec = count($filtradas);
					//...coordenadas aproximadas y fitradas!
					
					//2. OBTENER VELOCIDAD PROMEDIO EN CADA PUNTO DE RUTA					
					//...calcular los tiempos, distancias y velocidad entre cada par de coordenadas...
					//...usando diferentes resoluciones de las coordenadas del GPS...
					
					//Obetener velocidades entre una muestra de las coordenadas reales...
					$tiempo1 = strtotime($coords[0]['hora_enviado']);
					$tiempo2 = strtotime($coords[$n_coords_rec - 1]['hora_enviado']);
					$tiempo_total = ($tiempo2 - $tiempo1) / 60.0; //En minutos
										
					$sample_sizes = Array();
					$target_n_samples = 5;
					for ($i = 0; $i < $target_n_samples; $i++){
						$sample_size = pow(2, $i + 2);
						if ($sample_size <= $n_coords_rec){
							$sample_sizes[] = $sample_size;
						} else {
							break;
						}
					}
					
					$n_samples = count($sample_sizes);
					$sample_weights = Array();
					for ($i = 0; $i < $n_samples; $i++){
						$sample_weights[] = 1.0 / floatval($n_samples);
					}

					$velocidades_promedio = Array();
					for ($i = 0; $i < $n_coords_ruta - 1; $i++){
						$velocidades_promedio[$i] = 0.0;
					}
					
					//Para cada muestra....
					foreach ($sample_sizes as $ss_idx => $sample_size){			
						//Generar muestra...
						$muestra_ids = $this->distribute_uniform($sample_size, 0, $n_coords_rec - 1);
						
						$muestra_coordenadas = Array();
						$muestra_cercanos = Array();
						$muestra_factores = Array();
						foreach ($muestra_ids as $rel_idx => $muestra_idx){
							//...Copiar data...
							$muestra_coordenadas[$rel_idx] = $coords[$muestra_idx];
							$muestra_cercanos[$rel_idx] = $puntos_cercanos_idx[$muestra_idx];
							$muestra_factores[$rel_idx] = $puntos_cercanos_fac[$muestra_idx];
						}
						
						//extraer velocidades para muestra actual....
						$velocidades = $this->calcular_velocidades($muestra_coordenadas, $muestra_cercanos, 
																$muestra_factores, $ruta_distancias);
						
						//Agregar a promedio ponderado de velocidad por area...
						for ($k = 0; $k < count($muestra_ids) - 1; $k++){
							for ($i = $muestra_cercanos[$k]; $i < $muestra_cercanos[$k + 1]; $i++){
								$velocidades_promedio[$i] += $velocidades[$k] * $sample_weights[$ss_idx];
							}					
						}
						
					}	
					
					//...ahora asignar por cada par de segmento del poligono de la ruta...
					for ($i = 0; $i < $n_coords_ruta - 1; $i++){				
						$velocidades_coord_ruta[$i][] = $velocidades_promedio[$i];
					}
					
				}
				
				//Send data to database server...
				$data = Array();
				
				$promedio = 0.0;
				$desviacion = 0.0;
				$id_ruta_coord = 0;
				
				for ($i = 0; $i < $n_coords_ruta - 1; $i++){
					$id_ruta_coord = $puntos_ruta[$i]['id_ruta_coordenadas'];
					
					$n_velocidades = count($velocidades_coord_ruta[$i]);
					if ($n_velocidades > 0 ){
						$promedio = array_sum($velocidades_coord_ruta[$i]) / $n_velocidades;
						
						if ($n_velocidades > 1){
							//Sample Standard deviation.
							$desviacion = $this->desviacion_standard($velocidades_coord_ruta[$i]);
						} else {
							$desviacion = 0;
						}
					} else {
						$promedio = 0.0;
						$desviacion = 0.0;
					}
					
					$data[] = Array(
						"RutaCoordenada" => Array(
							"id_ruta_coordenadas" => $id_ruta_coord, 
							"velocidad_promedio" => $promedio,
							"velocidad_desviacion" => $desviacion,
						)
					);
				}
				$this->RutaCoordenada->saveMany($data, 
					Array(
						'atomic' => true,
						'validate' => false,
					)
				);				
				
				$mensaje = "Parametros para Ruta #".$id_ruta." han sido actualizados. Longitud de Ruta: ".$total_distance." KM.";
				
			}		
		} else {
			$id_ruta = 0;
			$id_recorrido_realizado = 0;
			$mensaje = null;
		}
		
		$this->set('todas_rutas', $rutas);
		
		$this->set('id_ruta', $id_ruta);
		$this->set('id_recorrido_realizado', $id_recorrido_realizado);
		$this->set('mensaje', $mensaje);
	}
		
	/*
		Script para calcular error prediccion....
	*/
	public function calcular_error(){
		//Extraer rutas...
		$rutas = $this->Ruta->find('all', array('recursive' => 0));
		$rutas = $this->_simplificar_resultados($rutas);
		
		$this->set('todas_rutas', $rutas);
	}	
	
	/*
		Emulacion de un recorrido pasado en tiempo real...., parecido a simulacion pero afecta la base de datos
	*/
	public function emulacion(){
		//Extraer rutas...
		$rutas = $this->Ruta->find('all', array('recursive' => 0));
		$rutas = $this->_simplificar_resultados($rutas);
		
		$this->set('todas_rutas', $rutas);
	}
	
	/*
		Recuperar estado de recorrido realizado...
	*/
	public function recuperar_estado_recorrido_realizado(){
		if (array_key_exists('id_recorrido_realizado', $this->request->query)){
			$id_recorrido_realizado = intval($this->request->query['id_recorrido_realizado']);
		} else {
			$id_recorrido_realizado = 0;
		}
		
		$recorrido_realizado = $this->RecorridoRealizado->find('all', Array(
			"recursive" => -1, 
			'conditions' => Array(
				'RecorridoRealizado.id_recorrido_realizado' => $id_recorrido_realizado,
			),
		));
		$recorrido_realizado = $this->_simplificar_resultados($recorrido_realizado);
				
		$this->set('recorrido_realizado', $recorrido_realizado);
	}
	
	/*
		Establecer estado de recorrido realizado...
	*/
	public function establecer_estado_recorrido_realizado(){
		$mensaje = "";
		
		if ($this->request->is('post')){
			$resultado = 1;
			
			$data = Array();
			//Recuperar valores de campos...
			if (array_key_exists('id_recorrido_realizado', $this->request->data)){			
				$data['id_recorrido_realizado'] = intval($this->request->data['id_recorrido_realizado']);
			} else {
				$resultado = 0;
				$mensaje = "id_recorrido_realizado debe especificarse";
			}
			
			if (array_key_exists('estado_recorrido_realizado', $this->request->data)){			
				$data['estado_recorrido_realizado'] = $this->request->data['estado_recorrido_realizado'];
			} else {
				$resultado = 0;
				$mensaje = "estado_recorrido_realizado debe especificarse";
			}
			if (array_key_exists('latitud', $this->request->data)){			
				$data['latitud'] = floatval($this->request->data['latitud']);
			} else {
				$resultado = 0;
				$mensaje = "latitud debe especificarse";
			}			
			if (array_key_exists('longitud', $this->request->data)){			
				$data['longitud'] = floatval($this->request->data['longitud']);
			} else {
				$resultado = 0;
				$mensaje = "longitud debe especificarse";
			}
			if (array_key_exists('velocidad_promedio', $this->request->data)){			
				$data['velocidad_promedio'] = floatval($this->request->data['velocidad_promedio']);
			} else {
				$resultado = 0;
				$mensaje = "velocidad_promedio debe especificarse";
			}
			if (array_key_exists('id_proxima_parada', $this->request->data)){			
				$data['id_proxima_parada'] = intval($this->request->data['id_proxima_parada']);
			} else {
				$resultado = 0;
				$mensaje = "id_proxima_parada debe especificarse";
			}
			
			if (array_key_exists('fecha_actualizacion_recorrido', $this->request->data)){			
				$data['fecha_actualizacion_recorrido'] = $this->request->data['fecha_actualizacion_recorrido'];
			} else {
				//Usar hora actual....
				$data['fecha_actualizacion_recorrido'] = date('Y/m/j H:i:s');
			}						
			
			if (array_key_exists('fecha_salida', $this->request->data)){			
				$data['fecha_salida'] = $this->request->data['fecha_salida'];
			} 
			
			//$data = Array("RecorridoRealizado" => $data);
			
			//$mensaje = implode("|",$this->request->data);
			//$mensaje = implode("|", array_keys($this->request->data));
			//$mensaje = "Len = ".count($this->request->data);
			//$resultado = 0;
			//$mensaje = $estado_recorrido_realizado;
			
			if ($resultado == 1){
				//Cargar ... 
				$record = $this->RecorridoRealizado->find('all', Array('conditions' => Array("RecorridoRealizado.id_recorrido_realizado" => $data["id_recorrido_realizado"])));
				if (count($record) == 0){
					$mensaje = "ID de recorrido realizado inválido";
					$resultado = 0;
				}else {
					$record = $record[0];
					foreach ($data as $llave => $valor){
						$record["RecorridoRealizado"][$llave] = $valor;
					}
				
					//$this->RecorridoRealizado->read(null, $data["RecorridoRealizado"]["id_recorrido_realizado"]);				
					//$this->RecorridoRealizado->set($data);
					//Intentar guardar....
					try {
						//if ($this->RecorridoRealizado->save()){
						if ($this->RecorridoRealizado->save($record)){
							$resultado = 1;
						} else {
							$resultado = 0;
							$mensaje = "Transaccion Base de datos fallida";
						}				
					} catch(Exception $e){
						$resultado = 0;
						$mensaje = "Transaccion Base de datos fallida: ".$e->getMessage();
					}
				}				
			} 
		} else {
			$resultado = 0;
			$mensaje = "Se esperaba POST";
		}
		
		$this->set('resultado', $resultado);
		$this->set('mensaje', $mensaje);
	}
	
	public function predicciones_data(){
		//Recupera toda la info necesaria para hacer predicciones para todas las rutas activas
		$data_rutas = Array();
		
		//Primero, encontrar las rutas existentes...
		$rutas = $this->Ruta->find('all', 
			array(
				'recursive' => -1,
				'conditions' => Array(
					'Ruta.estado_ruta' => 'activa',
				),
			)
		);
		$rutas = $this->_simplificar_resultados($rutas);
		
		foreach ($rutas as $idx => $valores_ruta){
			$info_ruta = Array();
			
			$info_ruta["id_ruta"] = $valores_ruta["id_ruta"];
			
			$coordenadas = $this->RutaCoordenada->find('all', 
				Array(
					'recursive' => -1,
					'conditions' => Array(
						'RutaCoordenada.id_ruta' => $info_ruta["id_ruta"],
					),
					'order' => Array('orden'),
				)
			);
			$coordenadas = $this->_simplificar_resultados($coordenadas);
			$info_ruta["coordenadas"] = $coordenadas;
			
			$paradas = $this->RutaParada->find('all', 
				Array(
					'recursive' => 0, 
					'conditions' => Array(
						'RutaParada.id_ruta' => $info_ruta["id_ruta"],
						'Parada.estado_parada' => 'activa',
					),
					'fields' => Array(
						'Parada.id_parada',
						'Parada.nombre_parada',
						'Parada.direccion_parada',
						'Parada.latitud',
						'Parada.longitud',
					),
					'order' => Array('RutaParada.orden_ruta'),
				)
			);
			$paradas = $this->_simplificar_resultados($paradas);
			$info_ruta["paradas"] = $paradas;
			
			
			$data_rutas[] = $info_ruta;
		}
		
		$this->set('data_rutas', $data_rutas);
	}
	
	/*==================================================================================
	/   Funciones auxiliares....
	/==================================================================================*/
	
	/*
		Calcula la distancia en kilometros entre dos coordenadas
	*/
	private function latlong_distance_km($lat1, $lon1, $lat2, $lon2){
		$R = 6371; // Radius of the earth in km
		$dLat = deg2rad($lat2 - $lat1);  // deg2rad below
		$dLon = deg2rad($lon2 - $lon1); 
		$a = sin($dLat/2.0) * sin($dLat/2.0) + 
			cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2.0) * sin($dLon/2.0); 
		$c = 2 * atan2(sqrt($a), sqrt(1.0 - $a)); 
		$d = $R * $c; // Distance in km
				
		return $d;
	}
	
	/*
		Calcula el punto mas cercano de la ruta a una coordenada dada con restriccion de velocidad maxima
	*/
	private function punto_cercano_ruta_rel($x_lat, $x_lon, $coordenadas_ruta, $ultima_posicion, $ultimo_factor, $delta_tiempo){
		
		$punto_cercano_lat = null;
		$punto_cercano_lon = null;
		$punto_cercano_tfact = null;
		$punto_cercano_idx = null;
		$distancia_cercana = null;
		
		$xp_lat = 0.0;
		$xp_lon = 0.0;
		$t_fact = 0.0;
		$t_dist = 0.0;
	
		$total_distancia = 0.0;
		$actual_factor = $ultimo_factor;
	
		$x_lat = floatval($x_lat);
		$x_lon = floatval($x_lon);	
		
		if ($delta_tiempo <= 0.0){
			$delta_tiempo = 0.001;
		}
		
		//para cada punto de la ruta...
		//(tratarles como valores X, Y)
		for ($i = $ultima_posicion; $i < count($coordenadas_ruta) - 1; $i++){
			$velocidad_requerida = $total_distancia / ($delta_tiempo / 3600.0);
			
			if ($velocidad_requerida > $this->PARAMETRO_promedio_vel_max){
				//No puede llegar tan lejos en el tiempo dado...
				break;
			}
			
			//Agregar la distancia actual a lo requerido...
			$lat1 = floatval($coordenadas_ruta[$i]['latitud']);
			$lon1 = floatval($coordenadas_ruta[$i]['longitud']);
			$lat2 = floatval($coordenadas_ruta[$i + 1]['latitud']);
			$lon2 = floatval($coordenadas_ruta[$i + 1]['longitud']);
			
								
			$total_distancia += $this->latlong_distance_km($lat1, $lon1, $lat2, $lon2)  * (1.0 - $actual_factor);			
			
			//calcular proyeccion...
			//t = dot(X - P1, P2 - P1) / POW(|P2 - P1|, 2)
			//usar t para aproximar!!! Xp = t * (P2 - P1) + P1
			
			//Vector a proyectar...
			$x_p1_lat = $x_lat - floatval($coordenadas_ruta[$i]['latitud']);
			$x_p1_lon = $x_lon - floatval($coordenadas_ruta[$i]['longitud']);
			
			//Vector en ruta...
			$p2_p1_lat = floatval($coordenadas_ruta[$i + 1]['latitud']) - floatval($coordenadas_ruta[$i]['latitud']);
			$p2_p1_lon = floatval($coordenadas_ruta[$i + 1]['longitud']) - floatval($coordenadas_ruta[$i]['longitud']);
			
			$t_fact = ($x_p1_lat * $p2_p1_lat + $x_p1_lon * $p2_p1_lon) / ($p2_p1_lat * $p2_p1_lat + $p2_p1_lon * $p2_p1_lon);
			
			if ($t_fact < $actual_factor){
				$xp_lat = floatval($coordenadas_ruta[$i]['latitud']) + $actual_factor * $p2_p1_lat;
				$xp_lon = floatval($coordenadas_ruta[$i]['longitud']) + $actual_factor * $p2_p1_lat;
				$t_fact = $actual_factor;
			} else {
				if ($t_fact > 1.0){
					$xp_lat = floatval($coordenadas_ruta[$i + 1]['latitud']);
					$xp_lon = floatval($coordenadas_ruta[$i + 1]['longitud']);
					$t_fact = 1.0;
				} else {
					$xp_lat = floatval($coordenadas_ruta[$i]['latitud']) + $t_fact * $p2_p1_lat;
					$xp_lon = floatval($coordenadas_ruta[$i]['longitud']) + $t_fact * $p2_p1_lon;
				}
			}					
			$actual_factor = 0.0;

			$t_dist = $this->latlong_distance_km($x_lat, $x_lon, $xp_lat, $xp_lon);
		
			//El mas cercano....
			if ($distancia_cercana == null || $distancia_cercana > $t_dist){
				$distancia_cercana = $t_dist;
				$punto_cercano_lat = $xp_lat;
				$punto_cercano_lon = $xp_lon;
				$punto_cercano_tfact = $t_fact;
				$punto_cercano_idx = $i;
			}

		}
		
		return Array(
			"latitud" => $punto_cercano_lat, 
			"longitud" => $punto_cercano_lon, 
			"distancia" => $distancia_cercana, 
			"tfact" => $punto_cercano_tfact,
			"index" => $punto_cercano_idx
		);			
	}
	
	/*
		Calcula el punto mas cercano de la ruta a una coordenada dada sin restricciones
	*/
	private function punto_cercano_ruta_abs($x_lat, $x_lon, $coordenadas_ruta){
		$punto_cercano_lat = null;
		$punto_cercano_lon = null;
		$punto_cercano_tfact = null;
		$punto_cercano_idx = null;
		$distancia_cercana = null;
		
		$xp_lat = 0.0;
		$xp_lon = 0.0;
		$t_fact = 0.0;
		$t_dist = 0.0;
		
		$x_lat = floatval($x_lat);
		$x_lon = floatval($x_lon);
		
		//para cada punto de la ruta...
		//(tratarles como valores X, Y)
		for ($i = 0; $i < count($coordenadas_ruta) - 1; $i++){
			//calcular proyeccion...
			//t = dot(X - P1, P2 - P1) / POW(|P2 - P1|, 2)
			//usar t para aproximar!!! Xp = t * (P2 - P1) + P1
			
			//Vector a proyectar...
			$x_p1_lat = floatval($x_lat) - floatval($coordenadas_ruta[$i]['latitud']);
			$x_p1_lon = floatval($x_lon) - floatval($coordenadas_ruta[$i]['longitud']);
			
			//Vector en ruta...
			$p2_p1_lat = floatval($coordenadas_ruta[$i + 1]['latitud']) - floatval($coordenadas_ruta[$i]['latitud']);
			$p2_p1_lon = floatval($coordenadas_ruta[$i + 1]['longitud']) - floatval($coordenadas_ruta[$i]['longitud']);
			
			$t_fact = ($x_p1_lat * $p2_p1_lat + $x_p1_lon * $p2_p1_lon) / ($p2_p1_lat * $p2_p1_lat + $p2_p1_lon * $p2_p1_lon);
			
			if ($t_fact < 0.0){
				$xp_lat = floatval($coordenadas_ruta[$i]['latitud']);
				$xp_lon = floatval($coordenadas_ruta[$i]['longitud']);
				$t_fact = 0.0;
			} else {
				if ($t_fact > 1.0){
					$xp_lat = floatval($coordenadas_ruta[$i + 1]['latitud']);
					$xp_lon = floatval($coordenadas_ruta[$i + 1]['longitud']);
					$t_fact = 1.0;
				} else {
					$xp_lat = floatval($coordenadas_ruta[$i]['latitud']) + $t_fact * $p2_p1_lat;
					$xp_lon = floatval($coordenadas_ruta[$i]['longitud']) + $t_fact * $p2_p1_lon;
				}
			}
			
			$t_dist = $this->latlong_distance_km($x_lat, $x_lon, $xp_lat, $xp_lon);
			
			//El mas cercano....
			if ($distancia_cercana == null || $distancia_cercana > $t_dist){
				$distancia_cercana = $t_dist;
				$punto_cercano_lat = $xp_lat;
				$punto_cercano_lon = $xp_lon;
				$punto_cercano_tfact = $t_fact;
				$punto_cercano_idx = $i;
			}
		}
			
		return Array(
			"latitud" => $punto_cercano_lat, 
			"longitud" => $punto_cercano_lon, 
			"distancia" => $distancia_cercana, 
			"tfact" => $punto_cercano_tfact,
			"index" => $punto_cercano_idx
		);		
	}
	
	/*
		Crea una muestra uniforme de numeros en un rango dado
	*/
	private function distribute_uniform($n, $start, $end){
		if ($n == 0){
			return Array();
		}
		if ($n == 1){
			return Array( intval(($start + $end) / 2.0));
		}
	
		$length = $end - $start;
		$result = Array();
		$salto = $length / ($n - 1);
		
		$actual = $start;
		for ($i = 0; $i < $n; $i++){
			$result[] = intval($actual);
			//$result[] = $actual;
			$actual += $salto;
		}
		
		return $result;
	}
	
	/*
		Calcula las velocidades entre segmentos de ruta definidos por un arreglo de coordenadas de GPS
	*/
	private function calcular_velocidades($coordinates, $puntos_cercanos, $factores_cercanos, $ruta_distancias){
		$diff_tiempos = Array();
		$diff_distancias = Array();
		$velocidades = Array();
		
		$n_coordinates = count($coordinates);
			
		$tiempo1 = strtotime($coordinates[0]['hora_enviado']);
		for ($i = 0; $i < $n_coordinates - 1; $i++){
			$tiempo2 = strtotime($coordinates[$i + 1]['hora_enviado']);
			
			//Differencia en segundos...
			$diff_actual = $tiempo2 - $tiempo1;
			$diff_tiempos[$i] = $diff_actual;
			
			$tiempo1 = $tiempo2;
			
			//Ahora, la diferencia en distancia...
			$total_dist = 0.0;			
			for ($k = $puntos_cercanos[$i]; $k <= $puntos_cercanos[$i + 1]; $k++){
				$start_factor = ($k == $puntos_cercanos[$i]) ? $factores_cercanos[$i] : 0.0;
				$end_factor = ($k == $puntos_cercanos[$i + 1]) ? $factores_cercanos[$i + 1]: 1.0;
				$rango_actual = $end_factor - $start_factor;
				
				$total_dist += $ruta_distancias[$k] * $rango_actual;					
			}
			$diff_distancias[$i] = $total_dist;
			
			if ($diff_tiempos[$i] > 0){
				$velocidades[$i] = ($diff_distancias[$i] / $diff_tiempos[$i]) * 60 * 60;
			} else {
				$velocidades[$i] = 0.0;
			}
		}
		
		return $velocidades;
	}
	
	/*
		Calcula la desviacion standard de un arreglo dado...
	*/
	private function desviacion_standard($arreglo){
		$n_elementos = count($arreglo);
		if ($n_elementos < 2){
			return 0.0;
		}
	
		$suma_total = array_sum($arreglo);
		$promedio = $suma_total / $n_elementos;
		
		$suma_num = 0.0;
		for ($i = 0; $i < $n_elementos; $i++){
			$diff = $arreglo[$i] - $promedio;
			
			$suma_num += $diff * $diff;			
		}
		
		//Muestral....
		return sqrt($suma_num / ($n_elementos - 1));
	}
}
?>